import { Compiler } from 'webpack';
import path from 'node:path';
import fs from 'node:fs';
import {
  extractChunksMap,
  extractMainScripts,
  extractMainStyles,
} from '../../utils/asset-extract.js';

export class InjectSW {
  static defaultOptions = {
    srcFile: '',
    withScripts: false,
  };

  options = InjectSW.defaultOptions;

  destFile = 'sw.js';

  srcFileContent = "self.addEventListener('fetch', () => { return; });";

  constructor(options: Partial<typeof InjectSW.defaultOptions> = {}) {
    this.options = { ...InjectSW.defaultOptions, ...options };
    try {
      const { srcFile } = this.options;
      if (srcFile && fs.existsSync(path.resolve(srcFile))) {
        this.srcFileContent = fs.readFileSync(srcFile, { encoding: 'utf-8' });
      }
    } catch {
      this.srcFileContent = "self.addEventListener('fetch', () => { return; });";
    }
  }

  apply(compiler: Compiler) {
    const pluginName = InjectSW.name;

    // webpack module instance can be accessed from the compiler object,
    // this ensures that correct version of the module is used
    // (do not require/import the webpack or any symbols from it directly).
    const { webpack } = compiler;

    // Compilation object gives us reference to some useful constants.
    const { Compilation } = webpack;

    // RawSource is one of the "sources" classes that should be used
    // to represent asset sources in compilation.
    const { RawSource } = webpack.sources;

    // Tapping to the "thisCompilation" hook in order to further tap
    // to the compilation process on an earlier stage.
    compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
      // Tapping to the assets processing pipeline on a specific stage.
      compilation.hooks.processAssets.tap(
        {
          name: pluginName,

          // Using one of the later asset processing stages to ensure
          // that all assets were already added to the compilation by other plugins.
          // stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
          stage: Compilation.PROCESS_ASSETS_STAGE_REPORT,
        },
        () => {
          if (this.options.withScripts) {
            const chunksMap = extractChunksMap(compilation.getStats());
            const filesToCache = chunksMap.chunks.map((c) => c.files).flat(2);
            const styles = extractMainStyles(chunksMap);
            const scripts = extractMainScripts(chunksMap);
            this.srcFileContent = `const __FILES=${JSON.stringify(
              filesToCache,
            )};
            const __STYLES=${JSON.stringify(styles)};
            const __SCRIPTS=${JSON.stringify(scripts)};
            ${this.srcFileContent}`;
          }
          // Adding new asset to the compilation, so it would be automatically
          // generated by the webpack in the output directory.
          compilation.emitAsset(
            this.destFile,
            new RawSource(this.srcFileContent),
          );
        },
      );
    });
  }
}
